package xsign

import (
	"crypto"
	"crypto/rsa"
	"encoding/base64"
	"errors"
	"github.com/google/go-querystring/query"
	"github.com/smartwalle/ncrypto"
	"github.com/smartwalle/nsign"
	"net/url"
	"os"
	"sync"
)

var (
	PublicKeyNotFound = errors.New("xsign: public key not found")
)

const (
	kFieldSign     = "sign"
	kFieldSignType = "sign_type"

	publicKeySN = "x-public-key"
)

type Client struct {
	mu sync.Mutex

	// 签名和验签
	signer    Signer
	verifiers map[string]Verifier

	certSN string
}

type Signer interface {
	SignValues(values url.Values, opts ...nsign.SignOption) ([]byte, error)

	SignBytes(data []byte, opts ...nsign.SignOption) ([]byte, error)
}

type Verifier interface {
	VerifyValues(values url.Values, signature []byte, opts ...nsign.SignOption) error

	VerifyBytes(data []byte, signature []byte, opts ...nsign.SignOption) error
}

type OptionFunc func(c *Client)

func New(privateKey string, opts ...OptionFunc) (nClient *Client, err error) {
	// 加载私钥
	priKey, err := ncrypto.DecodePrivateKey([]byte(privateKey)).PKCS1().RSAPrivateKey()
	if err != nil {
		priKey, err = ncrypto.DecodePrivateKey([]byte(privateKey)).PKCS8().RSAPrivateKey()
		if err != nil {
			return nil, err
		}
	}
	// 初始化客户端
	nClient = &Client{}
	nClient.signer = nsign.New(nsign.WithMethod(nsign.NewRSAMethod(crypto.SHA256, priKey, nil)))
	nClient.verifiers = make(map[string]Verifier)
	// 赋值其他配置
	for _, opt := range opts {
		if opt != nil {
			opt(nClient)
		}
	}

	return nClient, nil
}

func (c *Client) Sign(data interface{}) (signature string, err error) {
	// 结构体转URL query string
	val, err := query.Values(data)
	if err != nil {
		return
	}
	// 这里可以加入一些其他公共参数
	// 执行签名
	signature, err = c.sign(val)
	return
}

func (c *Client) sign(values url.Values) (signature string, err error) {
	sBytes, err := c.signer.SignValues(values)
	if err != nil {
		return "", err
	}
	signature = base64.StdEncoding.EncodeToString(sBytes)
	return signature, nil
}

func (c *Client) VerifySign(data interface{}, signature string) (err error) {
	values, err := query.Values(data)
	if err != nil {
		return
	}
	values.Add(kFieldSign, signature)
	return c.verifySign(values)
}

func (c *Client) verifySign(values url.Values) (err error) {
	var verifier Verifier
	if verifier, err = c.getVerifier(c.certSN); err != nil {
		return err
	}

	var signBytes []byte
	if signBytes, err = base64.StdEncoding.DecodeString(values.Get(kFieldSign)); err != nil {
		return err
	}

	return verifier.VerifyValues(values, signBytes, nsign.WithIgnore(kFieldSign, kFieldSignType))
}

func (this *Client) getVerifier(certSN string) (verifier Verifier, err error) {
	this.mu.Lock()
	defer this.mu.Unlock()

	verifier = this.verifiers[certSN]

	if verifier == nil {
		return nil, PublicKeyNotFound
	}
	return verifier, nil
}

// 加载公钥
func (this *Client) LoadXPublicKey(s string) error {
	var pub *rsa.PublicKey
	var err error
	if len(s) <= 0 {
		return PublicKeyNotFound
	}

	pub, err = ncrypto.DecodePublicKey([]byte(s)).PKIX().RSAPublicKey()
	if err != nil {
		return err
	}

	this.mu.Lock()
	this.loadVerifier(publicKeySN, pub)
	this.mu.Unlock()
	return nil
}

func (this *Client) LoadXPublicKeyFromFile(filename string) error {
	b, err := os.ReadFile(filename)
	if err != nil {
		return err
	}
	return this.LoadXPublicKey(string(b))
}

func (this *Client) loadVerifier(sn string, pub *rsa.PublicKey) Verifier {
	this.certSN = sn
	var verifier = nsign.New(nsign.WithMethod(nsign.NewRSAMethod(crypto.SHA256, nil, pub)))
	this.verifiers[this.certSN] = verifier
	return verifier
}
